﻿
using Google.ProtocolBuffers;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Threading.Tasks;
using System.Net;

namespace SLClient
{
    partial class CommunicatorClient
    {
        private const byte Head = 0xAA;

        private const byte Tail = 0xFF;

        public static EndPoint RemoteEndPoint
        {
            get;
            set;
        }

        private TcpChannel TcpChannel
        {
            get;
            set;
        }

        private bool IsConnected
        {
            get
            {
                return this.TcpChannel == null ? false : this.TcpChannel.State == TcpChannelState.Connected;
            }
        }

        public async Task<bool> ConnectAsync()
        {
            if (this.TcpChannel == null || this.TcpChannel.State != TcpChannelState.Disconnected)
            {
                this.TcpChannel = new TcpChannel();
                this.TcpChannel.Received += this.TcpChannelReceived;
                this.TcpChannel.Error += this.TcpChannelError;
            }

            return await this.TcpChannel.ConnectAsync(RemoteEndPoint);
        }

        private MemoryStream _stream;

        private void TcpChannelReceived(object sender, TcpChannelReceiveEventArgs e)
        {
            var data = e.Data;

            for (int i = 0; i < data.Length; i++)
            {
                byte value = data[i];

                switch (value)
                {
                    case Head:
                        this._stream = new MemoryStream();
                        break;
                    case Tail:
                        this._stream.Position = 0;
                        //Debug.WriteLine(Utility.ByteArrayToHexString(this.Stream.ToArray()));
                        this.Unpack(this._stream);
                        this._stream = null;
                        break;
                    default:
                        if (this._stream != null)
                        {
                            this._stream.WriteByte(data[i]);
                        }
                        break;
                }
            }
        }

        private Dictionary<int, TaskCompletionSource<IMessage>> _callDictionary = new Dictionary<int, TaskCompletionSource<IMessage>>();

        public async Task<TResponse> Call<TResponse>(ushort command, byte[] data)
        {
            var number = this.Pack(command, data);
            var tcs = new TaskCompletionSource<IMessage>();
            _callDictionary[number] = tcs;

            return (TResponse)(await tcs.Task);
        }

        #region Pack
        private static int SendNumber;

        private int Pack(ushort command, byte[] data)
        {
            SendNumber++;
            this.Pack(command, SendNumber, 0, data);
            return SendNumber;
        }

        private void Pack(ushort commandId, int sendNumber, int receiveNumber, byte[] data)
        {
            var stream = new MemoryStream();

            stream.WriteByte(Head);

            byte checksum = 0;

            byte[] header = new byte[15];
            header[0] = (byte)(commandId >> 8);
            header[1] = (byte)(commandId >> 0);
            header[2] = (byte)(data.Length >> 24);
            header[3] = (byte)(data.Length >> 16);
            header[4] = (byte)(data.Length >> 8);
            header[5] = (byte)(data.Length >> 0);
            header[6] = 0;
            header[7] = (byte)(sendNumber >> 24);
            header[8] = (byte)(sendNumber >> 16);
            header[9] = (byte)(sendNumber >> 8);
            header[10] = (byte)(sendNumber >> 0);
            header[11] = (byte)(receiveNumber >> 24);
            header[12] = (byte)(receiveNumber >> 16);
            header[13] = (byte)(receiveNumber >> 8);
            header[14] = (byte)(receiveNumber >> 0);

            for (int i = 0; i < header.Length; i++)
            {
                this.Write(stream, header[i]);
            }

            for (int i = 0; i < data.Length; i++)
            {
                this.Write(stream, data[i]);
            }

            this.Write(stream, checksum);

            stream.WriteByte(Tail);

            if (_sendQueue == null)
            {
                this.TcpChannel.Send(stream.ToArray());
            }
            else
            {
                _sendQueue.Enqueue(stream.ToArray());
            }
        }

        private void Write(MemoryStream stream, byte value)
        {
            switch (value)
            {
                case Head:
                    stream.WriteByte(HeadEscape);
                    stream.WriteByte(HeadEscape1);
                    break;
                case HeadEscape:
                    stream.WriteByte(HeadEscape);
                    stream.WriteByte(HeadEscape2);
                    break;
                case Tail:
                    stream.WriteByte(TailEscape);
                    stream.WriteByte(TailEscape1);
                    break;
                case TailEscape:
                    stream.WriteByte(TailEscape);
                    stream.WriteByte(TailEscape2);
                    break;
                default:
                    stream.WriteByte(value);
                    break;
            }
        }
        #endregion

        #region Unpack
        private int Unpack(MemoryStream stream)
        {
            ushort commandId;
            int sendNumber;
            int receiveNumber;
            byte[] data;

            this.Unpack(stream, out commandId, out sendNumber, out receiveNumber, out data);
            Debug.WriteLine("Unpack:CommandId:{0},ReceiveNumber:{1}", commandId, receiveNumber);

            //var message = this.GetMessage((CommandNumber)commandId, data);
            IMessage message = null;

            TaskCompletionSource<IMessage> tcs;
            if (_callDictionary.TryGetValue(receiveNumber, out tcs))
            {
                _callDictionary.Remove(receiveNumber);

                tcs.SetResult(message);
            }
            else
            {
                //this.OnReceive(receiveNumber, (CommandNumber)commandId, message);
            }

            return sendNumber;
        }

        private bool Unpack(MemoryStream stream, out ushort commandId, out int sendNumber, out int receiveNumber, out byte[] data)
        {
            byte header0 = (byte)stream.ReadByte();
            byte header1 = (byte)stream.ReadByte();
            commandId = (ushort)((header0 << 8) + header1);

            byte header2 = (byte)stream.ReadByte();
            byte header3 = (byte)stream.ReadByte();
            byte header4 = (byte)stream.ReadByte();
            byte header5 = (byte)stream.ReadByte();
            int length = (header2 << 24) + (header3 << 16) + (header4 << 8) + (header5 << 0);

            byte header6 = (byte)stream.ReadByte();

            byte header7 = (byte)stream.ReadByte();
            byte header8 = (byte)stream.ReadByte();
            byte header9 = (byte)stream.ReadByte();
            byte header10 = (byte)stream.ReadByte();
            sendNumber = (header7 << 24) + (header8 << 16) + (header9 << 8) + header10;

            byte header11 = (byte)stream.ReadByte();
            byte header12 = (byte)stream.ReadByte();
            byte header13 = (byte)stream.ReadByte();
            byte header14 = (byte)stream.ReadByte();
            receiveNumber = (header11 << 24) + (header12 << 16) + (header13 << 8) + header14;

            List<byte> list = new List<byte>();
            while (true)
            {
                byte value = (byte)stream.ReadByte();

                if (stream.Position == stream.Length)
                {
                    data = list.ToArray();
                    return list.Count == length;
                }

                list.Add(value);
            }
        }
        #endregion
    }
}
